package com.qf.leadnewswemedia.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qf.leadnewswemedia.mapper.WmNewsMapper;
import com.qf.leadnewswemedia.mapper.WmNewsMaterialMapper;
import com.qf.leadnewswemedia.service.WmMaterialService;
import com.qf.leadnewswemedia.service.WmNewsService;
import com.qf.qfleadnewscommons.minio.MinioOperator;
import com.qf.qfleadnewscommons.mvc.ThreadLocalUtils;
import com.qf.qfleadnewscommons.scan.ScanOperator;
import com.qf.qfleadnewscommons.scan.SuggestionResult;
import com.qf.qfleadnewsfeignapi.schedule.ScheduleApi;
import com.qf.qfleadnewsmodel.commons.consts.KafkaConst;
import com.qf.qfleadnewsmodel.commons.dtos.PageResponseResult;
import com.qf.qfleadnewsmodel.commons.dtos.ResponseResult;
import com.qf.qfleadnewsmodel.enums.AppHttpCodeEnum;
import com.qf.qfleadnewsmodel.enums.TaskTypeEnum;
import com.qf.qfleadnewsmodel.schedule.dtos.Task;
import com.qf.qfleadnewsmodel.wemedia.dtos.NewsDto;
import com.qf.qfleadnewsmodel.wemedia.dtos.NewsListDto;
import com.qf.qfleadnewsmodel.wemedia.pojos.WmMaterial;
import com.qf.qfleadnewsmodel.wemedia.pojos.WmNews;
import com.qf.qfleadnewsmodel.wemedia.pojos.WmNewsMaterial;
import com.qf.qfleadnewsutils.commons.ProtostuffUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService {

    @Autowired
    private WmNewsMaterialMapper newsMaterialMapper;

    @Autowired
    private WmMaterialService materialService;

    @Autowired
    private ApplicationContext ioc;

    @Autowired
    private ScanOperator scanOperator;

    @Autowired
    private MinioOperator minioOperator;

    @Autowired
    private KafkaTemplate kafkaTemplate;

    @Autowired
    private ScheduleApi scheduleApi;

    @Override
    public ResponseResult pageQuery(NewsListDto newsListDto) {

        newsListDto.checkParam();

        //当前用户id
        Long uid = ThreadLocalUtils.getUid();
        //关键字
        String keyword = newsListDto.getKeyword();
        //文章状态
        Integer status = newsListDto.getStatus();
        //频道
        Integer channelId = newsListDto.getChannelId();
        //发布日期
        Date begin = newsListDto.getBeginPubDate();
        Date end = newsListDto.getEndPubDate();

        IPage<WmNews> page = new Page<>(newsListDto.getPage(),newsListDto.getSize());

        Wrapper<WmNews> qw = Wrappers.lambdaQuery(WmNews.class)
                .eq(WmNews::getUserId,uid)
                .like(StringUtils.isNotBlank(keyword),WmNews::getTitle,keyword)
                .eq(status!=null,WmNews::getStatus,status)
                .eq(channelId!=null,WmNews::getChannelId,channelId)
                .between(begin!=null&&end!=null,WmNews::getPublishTime,begin,end)
                .orderByDesc(WmNews::getPublishTime);

        page(page,qw);

        PageResponseResult result = new PageResponseResult(page.getCurrent(),page.getSize(),page.getTotal());
        //设置页面需要的数据
        result.setData(page.getRecords());

        return result;
    }


    @Override
    public ResponseResult submit(NewsDto newsDto) {
        //保存文章
        WmNews wmNews = ioc.getBean(WmNewsService.class).saveNews(newsDto);

        //对文章内容实现扫描操作，审核的结果不应该影响文章保存的事务结果
//        //审核内容的执行不影响文章保存的结果返回，所以审核操作可以使用异步执行。（自定线程池 or @Async注解）
//        try {
//            ioc.getBean(WmNewsService.class).scanNews(wmNews);
//        }catch (Exception e){
//
//        }

        try{
            Date publishTime = wmNews.getPublishTime();
            if (publishTime.getTime() <= System.currentTimeMillis()){
                //立即发布，可以不添加到调度服务.立即审核发布
                ioc.getBean(WmNewsService.class).scanNews(wmNews);
            }else{
                delayPub(wmNews);
            }
        }catch (Exception e){
            e.printStackTrace();
        }


        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS.getCode(),AppHttpCodeEnum.SUCCESS.getErrorMessage());
    }

    private void delayPub(WmNews wmNews) {
        Task task = new Task();
        task.setTaskId(IdWorker.getId());
        task.setTaskType(TaskTypeEnum.NEWS_SCAN_TIME.getTaskType());
        task.setPriority(TaskTypeEnum.NEWS_SCAN_TIME.getPriority());
        task.setExecuteTime(wmNews.getPublishTime().getTime());
        /*
            任务参数：回头消费任务时，需要这个参数对我们消费业务做支撑
                ① 直接将这个wmnews对象转成byte[]作为参数传递 (不建议)
                ② 将wmnews对象的id作为参数传递
         */
        Integer wmNewsId = wmNews.getId();
        byte[] params = ProtostuffUtil.serialize(wmNewsId+"");
        task.setParameters(params);

        scheduleApi.addTask(task);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public WmNews saveNews(NewsDto newsDto) {
        WmNews wmNews = new WmNews();
        //要求目标对象的属性名和数据类型必须和源对象的属性名和数据类型一致
        BeanUtils.copyProperties(newsDto,wmNews);

        wmNews.setUserId(ThreadLocalUtils.getUid().intValue());

        //如果发布文章时，选择的自动封面，这里为了编数据库不支持负数值，暂时将其设置为无图
        if (newsDto.getType() == -1){
            wmNews.setType((short) 0);
        }

        wmNews.setCreatedTime(new Date());
        wmNews.setSubmitedTime(new Date());
        //发布文章时，没有选择定时发布，那么就是实时发布
        if (newsDto.getPublishTime() == null){
            wmNews.setPublishTime(new Date());
        }

        //对页面上传递过来的封面图片进行格式转换
        if (newsDto.getImages() !=null && newsDto.getImages().size()>0){
            String imagesStr = newsDto.getImages().stream().collect(Collectors.joining(","));
            wmNews.setImages(imagesStr);
        }

        wmNews.setEnable((short) 1);

        //执行数据库操作(根据id是否存在，确定是新增还是更新)
        saveOrUpdate(wmNews);

        //如果是保存草稿，程序到此结束
        if (newsDto.getStatus() == 0){
            return wmNews;
        }

        //维护news表和素材表的关系 (正文内容中素材 和 封面图片素材)
        newsAndMeterialRelation(wmNews,newsDto);

        return wmNews;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Async //表示这个方法在子线程中执行的
    public void scanNews(WmNews wmNews) throws UnsupportedEncodingException {
        //内容做审核
        //审核文本 （标题+content中的文本）
        /*List<String> texts = fetchTextFromContent(wmNews.getContent());
        texts.add(wmNews.getTitle());

        String content = texts.stream().collect(Collectors.joining(","));

        List<SuggestionResult> results = scanOperator.scanText(content);

        boolean flag = processSuggestionResult(results,wmNews);

        if (!flag) return;

        //审核图片
        //封面图片
        Set<String> images = new HashSet<>(); //去重
        String imagesStr = wmNews.getImages();
        if (StringUtils.isNotBlank(imagesStr)){
            String[] split = imagesStr.split(",");
            List<String> fmImages = Arrays.asList(split);
            images.addAll(fmImages);
        }
        //内容图片
        List<String> contentImages = fetchImagesFromContent(wmNews.getContent());
        images.addAll(contentImages);

        for (String image : images) {
            byte[] bytes = minioOperator.downLoadFile(image);
            results = scanOperator.scanImage(bytes);

            flag = processSuggestionResult(results,wmNews);

            if (!flag) return;
        }*/

        //文本和图片都审核通过
        wmNews.setStatus((short) 8);
        updateById(wmNews);

        //发布文章到文章服务
        try {
            log.info("-----------发布文章开始，newsId:{}--------",wmNews.getId());
            kafkaTemplate.send(KafkaConst.NEWS_PUBLISH_TOPIC, wmNews.getId() + "");
            log.info("-----------发布文章完成，newsId:{}--------",wmNews.getId());
        }catch (Exception e){
            e.printStackTrace();
            //重新发送 (记录重发的次数)
        }
    }

    /**
     * 处理审核结果
     * @param results
     * @param wmNews
     * @return
     */
    private boolean processSuggestionResult(List<SuggestionResult> results,WmNews wmNews){
        for (SuggestionResult result : results) {
            if ("review".equals(result.getSuggestion())){
                //要人工审核
                wmNews.setStatus((short) 3);
                wmNews.setReason(result.getLabel());
                updateById(wmNews);
                return false;
            }else if("block".equals(result.getSuggestion())){
                //审核失败
                wmNews.setStatus((short) 2);
                wmNews.setReason(result.getLabel());
                updateById(wmNews);
                return false;
            }
        }

        return true;
    }


    private void newsAndMeterialRelation(WmNews wmNews, NewsDto newsDto) {
        //文章内容中素材关系维护 （保障newsId+meterialId 唯一的）
        // ① 要将原来和这个newsId相关的关系数据删除
        newsMaterialMapper.delete(Wrappers.lambdaQuery(WmNewsMaterial.class).eq(WmNewsMaterial::getNewsId,wmNews.getId()));
        // ② 维护新的关系 (关联内容素材和素材表关系)
        List<String> contentImages = fetchImagesFromContent(wmNews.getContent());

        //保存内容素材和素材表关系
        saveRelation(wmNews, contentImages, (short) 0);

        //③ 关联封面图片素材
        List<String> images = newsDto.getImages();

        //如果是自动图，我们需要从内容中提取封面图片
        Short type = newsDto.getType();
        if (type == -1){
            int size = contentImages==null?0:contentImages.size();
            if (size >= 3){
                //从内容中取3张图片作为封面
                images = contentImages.stream().limit(3).collect(Collectors.toList());
            }else if(size >= 1) {
                //从内容中取1张图片作为封面
                images = contentImages.stream().limit(1).collect(Collectors.toList());
            }
        }

        if (images != null && images.size()>0){

            //更新news表中封面图片信息
            String imagesStr = images.stream().collect(Collectors.joining(","));
            wmNews.setImages(imagesStr);
            updateById(wmNews);

            //有封面图片，关联封面素材关系
            saveRelation(wmNews, images, (short) 1);
        }


    }

    /**
     * 保存素材关系表
     * @param wmNews
     * @param images
     */
    private void saveRelation(WmNews wmNews, List<String> images,short type) {
        if (images != null && images.size()>0){
            List<WmNewsMaterial> newsMaterialList = new ArrayList<>();
            for (int i = 0; i < images.size(); i++) {
                WmNewsMaterial newsMaterial = new WmNewsMaterial();
                newsMaterial.setNewsId(wmNews.getId());

                //通过素材的url地址查询对应的素材id
                String url = images.get(i);
                WmMaterial wmMaterial = materialService.getOne(Wrappers.lambdaQuery(WmMaterial.class).eq(WmMaterial::getUrl, url));
                //设置素材id
                newsMaterial.setMaterialId(wmMaterial.getId());
                //设置顺序号
                newsMaterial.setOrd((short) i);
                //设置素材类型（内容素材-0，封面素材-1）
                newsMaterial.setType(type);

                newsMaterialList.add(newsMaterial);
            }

            //数据库保存
            newsMaterialMapper.batchSave(newsMaterialList);
        }
    }

    /**
     * 从内容中提取图片素材
     * @param content
     * @return
     */
    private List<String> fetchImagesFromContent(String content) {

        // [{type:text,value:xxxx},{type:image,value:uuuu}]
        List<Map> mapList = JSONArray.parseArray(content, Map.class);

        List<String> images = mapList.stream()
                //过滤流中的数据，过滤后，剩下的数据都是image类型
                .filter(map -> {
                    String type = (String) map.get("type");
                    return type.equals("image");
                })
                //将流中的map类型数据转成String类型数据
                .map(map -> {
                    String url = (String) map.get("value");
                    return url;
                })
                //将String类型的流收集成String类型的集合
                .collect(Collectors.toList());


        return images;
    }
    /**
     * 从内容中提取文本素材
     * @param content
     * @return
     */
    private List<String> fetchTextFromContent(String content) {

        // [{type:text,value:xxxx},{type:image,value:uuuu}]
        List<Map> mapList = JSONArray.parseArray(content, Map.class);

        List<String> texts = mapList.stream()
                //过滤流中的数据，过滤后，剩下的数据都是image类型
                .filter(map -> {
                    String type = (String) map.get("type");
                    return type.equals("text");
                })
                //将流中的map类型数据转成String类型数据
                .map(map -> {
                    String url = (String) map.get("value");
                    return url;
                })
                //将String类型的流收集成String类型的集合
                .collect(Collectors.toList());


        return texts;
    }


    public static void main(String[] args) {
//        List<String> list = Arrays.asList("abc", "def", "gez");
//
//        String str = list.stream().collect(Collectors.joining(","));
//
//        System.out.println(str);

        String content = "[{\"type\":\"text\",\"value\":\"2207-测试202207-测试202207-测试202207-测试202207-测试202207-测试202207-测试202207-测试202207-测试202207-测试202207-测试202207-测试202207-测试202207-测试202207-测试202207-测试202207-测试20\"},{\"type\":\"image\",\"value\":\"http://localhost:9005/leadnews/2023/02/14/27089f12155d40ea89e2090ec6d874fb.jpg\"},{\"type\":\"image\",\"value\":\"http://localhost:9005/leadnews/2023/02/13/b2596560af294b7cb782ab1b45a26601.jpg\"},{\"type\":\"text\",\"value\":\"请在这里输入正文\"}]";

        List<String> images = new WmNewsServiceImpl().fetchImagesFromContent(content);

        System.out.println(images);
    }
}
